package edu.thu.thss.yxy.launcher;

import javax.swing.JOptionPane;

import edu.thu.thss.yxy.configuration.ConfigurationManager;
import edu.thu.thss.yxy.gui.GuiManager;
import edu.thu.thss.yxy.gui.event.*;
import edu.thu.thss.yxy.media.*;
import edu.thu.thss.yxy.media.event.*;
import edu.thu.thss.yxy.network.NetworkAddressManager;
import edu.thu.thss.yxy.sip.*;
import edu.thu.thss.yxy.sip.event.CallEvent;
import edu.thu.thss.yxy.sip.event.CallListener;
import edu.thu.thss.yxy.sip.event.CallStateEvent;
import edu.thu.thss.yxy.sip.event.CallStateListener;
import edu.thu.thss.yxy.util.Logger;
import edu.thu.thss.yxy.util.PropertiesUtil;

public class GoPhone implements MediaListener, CallListener, CallStateListener, CallPeerListener {
	/**
	 * Our logger.
	 */
	private final Logger logger = Logger.getLogger(MediaManager.class);

	private enum GoPhoneMode {
		CONFIG_MODE, PHONE_MODE
	};

	private GoPhoneMode curMode = GoPhoneMode.PHONE_MODE;

	protected GuiManager guiManager = null;
	protected MediaManager mediaManager = null;
	protected SipManager sipManager = null;
	protected Object unregistrationLock = new Object();

	public GoPhone() {
		loadProperties();
		guiManager = new GuiManager();
		mediaManager = new MediaManager();
		sipManager = new SipManager();
		guiManager.addCallPeerListener(this);
		guiManager.setProperties(PropertiesUtil.getCurrProperties());
	}

	private void loadProperties() {
		ConfigurationManager.loadProperties();
	}

	public void config() {
		curMode = GoPhoneMode.CONFIG_MODE;
		guiManager.showConfigFrame();
	}

	public void launch() {
		curMode = GoPhoneMode.PHONE_MODE;

		try {
			mediaManager.start();
		} catch (MediaException exc) {
			logger.error("Failed to start mediaManager", exc);
			JOptionPane.showMessageDialog(null,
					"The following exception occurred while initializing media!\n"
							+ exc.getMessage());
		}
		mediaManager.addMediaListener(this);

		// new NetworkAddressManager().start();

		sipManager.addCallListener(this);
		// sipManager.setSecurityAuthority(this);
		guiManager.showMainFrame();
		// put in a seperate thread

		try {
			sipManager.start();
			if (sipManager.isStarted()) {
				guiManager.setCommunicationActionsEnabled(true);
				logger.trace("sipManager appears to be successfully started");
			}
		} catch (OperationFailedException exc) {
			logger.error("An exception occurred while initializing communication stack!\n"
						+ "You won't be able to send or receive calls",
							exc);
			JOptionPane.showMessageDialog(null,
					"An exception occurred while initializing communication stack!\n"
							+ "You won't be able to send or receive calls");
		}

		try {
			sipManager.register();
		} catch (OperationFailedException exc) {
			logger.error("Failed to register!\n" + exc.getMessage() + "\n"
					+ "This is a warning only. The phone would still function",
					exc);

		}
	}

	public void shutdown() {
		try {
			logger.logEntry();
			// close all media streams
			// close capure devices
			try {
				mediaManager.closeStreams();
			} catch (Exception exc) {
				logger.info("Could not properly close media streams!\n", exc);
			} catch (Throwable exc) {
				logger.error("Failed to properly close media streams", exc);
			}

			try {
				mediaManager.stop();
			} catch (Throwable ex) {
				logger.error("Failed to properly stop Media Manager", ex);
			}

			// close all sip calls
			try {
				sipManager.endAllCalls();
			} catch (Throwable exc) {
				logger.error("Failed to properly end active callse", exc);
			}
			// unregister
			try {
				sipManager.unregister();
			} catch (Throwable exc) {
				logger.error("Failed to properly unregister", exc);
			}

			NetworkAddressManager.shutdown();
		} catch (Exception ex) {
			logger.error("Failed to properly shut down.", ex);
		} finally {
			logger.logExit();
			System.exit(0);
		}
	}

	public void mediaStatusChanged() {
		guiManager.removePlayerComponents();
	}

	public void receivedMediaStream(MediaEvent evt) {
		javax.media.Player player = (javax.media.Player) evt.getSource();
		guiManager.addVisualComponent(player.getVisualComponent());
		guiManager.addControlComponent(player.getControlPanelComponent());
	}

	public void handleAnswerRequest(CallPeerControlEvent evt) {
		CallPeer callPeer = (CallPeer) evt.getAssociatedCallPeer();
		if (!callPeer.getState().equals(CallState.ALERTING)) {
			return;
		}
		String sdpData = null;
		try {
			sdpData = mediaManager.generateSdpDescription();
		} catch (MediaException ex) {
			logger.error("Failed to Generate an SDP description", ex);
			try {
				sipManager.sendServerInternalError(callPeer.getPeerID());
			} catch (OperationFailedException ex1) {
				logger.error(ex1.getMessage(), ex1);
			}
			return;
		}
		try {
			sipManager.answerCall(callPeer.getPeerID(), sdpData);
		} catch (OperationFailedException exc) {
			logger.error("Could not answer call!\nError was: "
					+ exc.getMessage(), exc);
		}
	}

	public void handleDialRequest(CallInitiationEvent evt) {
		String callee = (String) evt.getSource();
		String sdpData = null;
		try {
			sdpData = mediaManager.generateSdpDescription();
		} catch (MediaException ex) {
			logger.error("Failed to Generate an SDP description", ex);
			return;
		}
		try {
			Call call = sipManager.createCall(callee, sdpData);
			call.addCallStateListener(this);
			CallPeer callPeer = new CallPeer(call);
			guiManager.addCallPeer(callPeer);
		} catch (OperationFailedException exc) {
			logger.error("Could not establish call!\nError was: "
					+ exc.getMessage(), exc);
		}
	}

	public void handleExitRequest() {
		shutdown();
	}

	public void handleHangupRequest(CallPeerControlEvent evt) {
		try {
			sipManager.hangupCall(evt.getAssociatedCallPeer().getPeerID());
		} catch (OperationFailedException exc) {
			logger.error("Could not properly terminate call!\n"
                        + "(This is not a fatal error)",
                         exc
                        );
		}

	}

	public void handlePropertiesSaveRequest() {
		ConfigurationManager.storeProperties();
		if(curMode == GoPhoneMode.CONFIG_MODE){
			System.exit(0);
		}
	}

	public void callStateChanged(CallStateEvent evt) {
		Call call = evt.getSourceCall();
		if (evt.getNewState() == CallState.CONNECTED)
			try {
				mediaManager.openMediaStreams(call.getRemoteSdpDescription());
			} catch (MediaException ex) {
				logger.error("The following exception occurred while trying to open media connection:\n"
								+ ex.getMessage(), ex);
			}
		else if (evt.getNewState() == CallState.DISCONNECTED
				||evt.getNewState() == CallState.FAILED)
			mediaManager.closeStreams();
	}

	public void registered(RegistrationEvent evt)
    {
        guiManager.setGlobalStatus(RegistrationState.REGISTERED,
                                   evt.getReason());
    }

    public void registering(RegistrationEvent evt)
    {
        guiManager.setGlobalStatus(RegistrationState.REGISTERING,
                                   evt.getReason());
    }


    public void unregistered(RegistrationEvent evt)
    {
        guiManager.setGlobalStatus(RegistrationState.UNREGISTERED,
                                   evt.getReason());
    }

	public void incomingCallReceived(CallEvent evt) {
		Call call = evt.getSourceCall();
        CallPeer callPeer = new CallPeer(call);
        guiManager.addCallPeer(callPeer);
        call.addCallStateListener(this);
	}

	public void outgoingCallCreated(CallEvent evt) {
		Call call = evt.getSourceCall();
        CallPeer callPeer = new CallPeer(call);
        guiManager.addCallPeer(callPeer);
        call.addCallStateListener(this);
	}
	
	public void callEnded(CallEvent event) {
		Call call = event.getSourceCall();
		call.removeCallStateListener(this);
	}
	
	public static void main(String[] args) {
		GoPhone phone = new GoPhone();
		phone.launch();
	}
}
